#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/string.h>

#include "blk.h"

static struct request all_requests[NR_REQUEST];

struct wait_queue *wait_for_request = NULL;

struct blk_dev_struct blk_dev[MAX_BLKDEV] = {
    {NULL, NULL}, /* no_dev */
    {NULL, NULL}, /* dev mem */
    {NULL, NULL}, /* dev fd */
    {NULL, NULL}, /* dev hd */
    {NULL, NULL}, /* dev ttyx */
    {NULL, NULL}, /* dev tty */
    {NULL, NULL}, /* dev lp */
    {NULL, NULL}, /* dev pipes */
    {NULL, NULL}, /* dev sd */
    {NULL, NULL}  /* dev st */
};

int *blk_size[MAX_BLKDEV] = {
    NULL,
    NULL,
};

int *blksize_size[MAX_BLKDEV] = {
    NULL,
    NULL,
};

static inline struct request *get_request(int n, int dev) {
    static struct request *prev_found = NULL, *prev_limit = NULL;
    register struct request *req, *limit;

    if (n <= 0)
        panic("get_request(%d): impossible!\n", n);

    limit = all_requests + n;
    if (limit != prev_limit) {
        prev_limit = limit;
        prev_found = all_requests;
    }
    req = prev_found;
    for (;;) {
        req = ((req > all_requests) ? req : limit) - 1;
        if (req->dev < 0)
            break;
        if (req == prev_found)
            return NULL;
    }
    prev_found = req;
    req->dev = dev;
    return req;
}

static long ro_bits[MAX_BLKDEV][8];

int is_read_only(int dev) {
    int minor, major;

    major = MAJOR(dev);
    minor = MINOR(dev);
    if (major < 0 || major >= MAX_BLKDEV)
        return 0;
    return ro_bits[major][minor >> 5] & (1 << (minor & 31));
}

static void add_request(struct blk_dev_struct *dev, struct request *req) {
    struct request *tmp;

    req->next = NULL;
    cli();
    if (req->bh)
        req->bh->b_dirt = 0;
    if (!(tmp = dev->current_request)) {
        dev->current_request = req;
        (dev->request_fn)();
        sti();
        return;
    }
    for (; tmp->next; tmp = tmp->next) {
        if ((IN_ORDER(tmp, req) ||
             !IN_ORDER(tmp, tmp->next)) &&
            IN_ORDER(req, tmp->next))
            break;
    }
    req->next = tmp->next;
    tmp->next = req;

    sti();
}

static void make_request(int major, int rw, struct buffer_head *bh) {
    unsigned int sector, count;
    struct request *req;
    int max_req;

    if (rw != READ && rw != WRITE) {
        printk("Bad block dev command, must be R/W/RA/WA\n");
        return;
    }
    count = bh->b_size >> 9;
    sector = bh->b_blocknr * count;
    if (blk_size[major])
        if (blk_size[major][MINOR(bh->b_dev)] < (sector + count) >> 1) {
            bh->b_dirt = bh->b_uptodate = 0;
            return;
        }

    max_req = (rw == READ) ? NR_REQUEST : ((NR_REQUEST * 2) / 3);

repeat:
    cli();

    if ((major == HD_MAJOR) && (req = blk_dev[major].current_request)) {
        req = req->next;
        while (req) {
            if (req->dev == bh->b_dev &&
                req->cmd == rw &&
                req->sector + req->nr_sectors == sector &&
                req->nr_sectors < 254) {
                req->nr_sectors += count;
                bh->b_dirt = 0;
                sti();
                return;
            }

            if (req->dev == bh->b_dev &&
                req->cmd == rw &&
                req->sector - count == sector &&
                req->nr_sectors < 254) {
                req->nr_sectors += count;
                bh->b_reqnext = req->bh;
                req->buffer = bh->b_data;
                req->sector = sector;
                bh->b_dirt = 0;
                req->bh = bh;
                sti();
                return;
            }

            req = req->next;
        }
    }

    req = get_request(max_req, bh->b_dev);
    if (!req) {
        sleep_on(&wait_for_request);
        sti();
        goto repeat;
    }

    /* we found a request. */
    sti();

    /* fill up the request-info, and add it to the queue */
    req->cmd = rw;
    req->sector = sector;
    req->nr_sectors = count;
    req->buffer = bh->b_data;
    req->bh = bh;
    req->next = NULL;
    add_request(major + blk_dev, req);
}

void ll_rw_block(int rw, int nr, struct buffer_head *bh[]) {
    unsigned int major;
    struct request plug;
    int plugged;
    int correct_size;
    struct blk_dev_struct *dev;
    int i;

    dev = NULL;
    if ((major = MAJOR(bh[0]->b_dev)) < MAX_BLKDEV)
        dev = blk_dev + major;
    if (!dev || !dev->request_fn) {
        printk(
            "ll_rw_block: Trying to read nonexistent block-device %04lX (%ld)\n",
            (unsigned long)bh[0]->b_dev, bh[0]->b_blocknr);
        goto sorry;
    }

    correct_size = BLOCK_SIZE;
    if (blksize_size[major]) {
        i = blksize_size[major][MINOR(bh[0]->b_dev)];
        if (i)
            correct_size = i;
    }

    /* Verify requested block sizees.  */
    for (i = 0; i < nr; i++) {
        if (bh[i] && bh[i]->b_size != correct_size) {
            printk(
                "ll_rw_block: only %d-char blocks implemented (%lu)\n",
                correct_size, bh[i]->b_size);
            goto sorry;
        }
    }

    if (rw == WRITE && is_read_only(bh[0]->b_dev)) {
        printk("Can't write to read-only device 0x%X\n", bh[0]->b_dev);
        goto sorry;
    }

    plugged = 0;
    cli();
    if (!dev->current_request && nr > 1) {
        dev->current_request = &plug;
        plug.dev = -1;
        plug.next = NULL;
        plugged = 1;
    }
    sti();
    for (i = 0; i < nr; i++) {
        if (bh[i]) {
            bh[i]->b_req = 1;
            make_request(major, rw, bh[i]);
        }
    }
    if (plugged) {
        cli();
        dev->current_request = plug.next;
        (dev->request_fn)();
        sti();
    }
    return;

sorry:
    for (i = 0; i < nr; i++) {
        if (bh[i])
            bh[i]->b_dirt = bh[i]->b_uptodate = 0;
    }
}

void blk_dev_init() {
    struct request *req;

    req = all_requests + NR_REQUEST;
    while (--req >= all_requests) {
        req->dev = -1;
        req->next = NULL;
    }
    memset(ro_bits, 0, sizeof(ro_bits));
    hd_init();
}
